package com.mar.quarkus.jwt.generator;

import io.smallrye.jwt.build.Jwt;
import io.smallrye.jwt.build.JwtClaimsBuilder;
import io.vertx.core.json.JsonObject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.jwt.Claims;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

/**
 * Description: Token的生成 .<br>
 *
 * @author m-xy
 *     Created By 2020/4/23 10:57
 */
@ApplicationScoped
public class TokenGenerator {
    private final static Logger logger = LoggerFactory.getLogger(TokenGenerator.class);
    @ConfigProperty(name = "mp.jwt.verify.private_key_resource", defaultValue = "/rsa_private_key_pkcs8.pem")
    public String privateKeyResource;
    @ConfigProperty(name = "mp.jwt.verify.issuer", defaultValue = "jiaomatech")
    public String issuer;
    @ConfigProperty(name = "mp.jwt.verify.subject", defaultValue = "jm")
    public String subject;
    @ConfigProperty(name = "mp.jwt.verify.expires_in", defaultValue = "86400")
    public Integer defaultExpirationInSeconds;
    private PrivateKey privateKey;

    @PostConstruct
    public void readPrivateKey() {
        String encodedPem = null;
        final String errorMessage = "An exception occured while reading private key";
        // 读取PEM文件
        try (InputStream is = TokenGenerator.class.getResourceAsStream(privateKeyResource)) {
            byte[] tmp = new byte[4096];
            int length = is.read(tmp);
            encodedPem = new String(tmp, 0, length, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new RuntimeException(errorMessage, e);
        }
        // 去掉头尾
        encodedPem = encodedPem
            .replaceAll("-----BEGIN (.*)-----", "")
            .replaceAll("-----END (.*)----", "")
            .replaceAll("\r\n", "")
            .replaceAll("\n", "");
        // 编码解析
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(encodedPem.getBytes()));
        KeyFactory kf;
        try {
            kf = KeyFactory.getInstance("RSA");
            privateKey = kf.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException(errorMessage, e);
        }
    }

    public JsonObject generateTokenString(String userId, String username, Set<String> groups) {
        return generateTokenString(userId, username, null, groups);
    }

    public JsonObject generateTokenString(String userId, String username, Map<String, Object> claims,
                                          Set<String> groups) {
        JsonObject rt = new JsonObject();
        try {
            String jti = UUID.randomUUID().toString();
            int currentTimeInSecs = currentTimeInSecs();
            int exp = currentTimeInSecs + defaultExpirationInSeconds;
            JwtClaimsBuilder builder = Jwt.claims(Optional.ofNullable(claims).orElse(new HashMap<>(0)));
            Optional.ofNullable(issuer).ifPresent(v -> builder.issuer(v));
            Optional.ofNullable(subject).ifPresent(v -> builder.subject(v));
            Optional.ofNullable(userId).ifPresent(v -> builder.upn(v));
            Optional.ofNullable(username).ifPresent(v -> builder.claim(Claims.preferred_username.name(), v));

            builder.claim(Claims.jti.name(), jti);
            builder.issuedAt(currentTimeInSecs);
            builder.expiresAt(exp);
            builder.groups(groups);
            // kid表示算法所使用的密钥文件（当服务端需要多个密钥文件时使用）
            // .signatureKeyId("*.pem")
            String token = builder.jws().sign(privateKey);
            rt.put("access_token", token);
            rt.put(Claims.jti.name(), jti);
            rt.put(Claims.exp.name(), exp);
        } catch (Exception e) {
            logger.error("Jwt generate fail", e);
        }
        return rt;
    }

    /**
     * 当前时间/秒.
     *
     * @return the current time in seconds since epoch
     */
    private int currentTimeInSecs() {
        long currentTimeMillis = System.currentTimeMillis();
        return (int) (currentTimeMillis / 1000);
    }
}
